/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package nsip.components.ds;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import nsip.util.LazyInitializingMap;

/**
 * A database which keeps track of item prices and allows some limited 
 * prediction to the future.
 * 
 * @author nico.rehwaldt
 */
public class PriceTrackerDataBase {

    private int currentDate = 0;
    
    private ArrayList<PriceTracker> trackers = new ArrayList(20);
    private ProductPriceCache productPriceCache = new ProductPriceCache();
    
    /**
     * Adds a price to this tracker.
     *
     * @param productID
     * @param dueDate
     * @param price
     */
    public void addPrice(int productID, int dueDate, int price) {
        PriceTracker tracker = getTracker(dueDate);
        tracker.add(productID, price);
    }

    private PriceTracker getTracker(int dueDate) {
        int index = dueDate - currentDate;
        if (index < 0) { index = 0; }

        while (index >= trackers.size()) {
            trackers.add(new PriceTracker());
        }

        return trackers.get(index);
    }

    /**
     * Return the price of the specified product on the selected day
     *
     * @param productID
     * @param day
     * @return
     */
    public int getPrice(int productID, int day) {
        return getTracker(day).currentPrice(productID);
    }

    /**
     * 
     * @param componentIDs
     * @param day
     *
     * @return price for all components or -1 if price for some component does
     *         not exist
     */
    public int getComponentPrices(int productID, Catalog catalog, int day) {
        Integer sum = productPriceCache.get(productID).get(day);
        if (sum != null) {
            return sum;
        }
        
        sum = 0;
        Integer [] componentIDs = catalog.getInfo(productID).getComponentIDs();
        
        for (int partID: componentIDs) {
            int partPrice = getPrice(partID, day);
            if (partPrice == 0) {
                sum = -1;
                break;
            }
            sum += partPrice;
        }

        productPriceCache.get(productID).put(day, sum);
        
        return sum;
    }

    /**
     * Updates the db for the next day
     *
     * @param currentDay
     */
    public void updateForNextDay(int currentDay) {
        this.currentDate = currentDay;
        
        PriceTracker last = null;

        for (PriceTracker tracker: trackers) {
            tracker.accumulateAndReset();
            if (last != null) {
                for (Map.Entry<Integer, Integer> price: tracker.getDayPrices().entrySet()) {
                    last.add(price.getKey(), price.getValue());
                }
            }

            last = tracker;
        }
        
        this.productPriceCache.clear();
    }

    /**
     * Price tracker which keeps track of prices of products on a single day.
     */
    private static class PriceTracker {

        private LazyListMap dayPrices = new LazyListMap();
        private LazyListMap history = new LazyListMap();

        private Map<Integer, Integer> currentPriceMap = new HashMap();

        /**
         * Accumulate prices and reset this tracker.
         */
        public void accumulateAndReset() {
            currentPriceMap.clear();

            for (Map.Entry<Integer, List<Integer>> e: dayPrices.entrySet()) {
                int productID = e.getKey();
                List<Integer> prices = e.getValue();

                int sum = 0;
                int count = 0;

                for (int price: prices) {
                    sum += price;
                    count++;
                }

                List<Integer> priceHistory = history.get(productID);
                int acumulatedDayPrice = sum != 0 ? sum / count :
                    (priceHistory.isEmpty() ? 0 :
                            priceHistory.get(priceHistory.size() - 1));

                priceHistory.add(acumulatedDayPrice);
                currentPriceMap.put(productID, acumulatedDayPrice);

                prices.clear();
            }
        }

        /**
         * Adds the specified price for the given product
         * @param productID
         * @param price
         */
        public void add(int productID, int price) {
            dayPrices.get(productID).add(price);
        }

        /**
         * Returns current price of product.
         *
         * @param productID
         * @return
         */
        public int currentPrice(int productID) {
            Integer price = currentPriceMap.get(productID);
            return price != null ? price : 0;
        }

        /**
         * Returns a map which maps productID -> price and contains todays
         * estimated prices for products.
         *
         * @return
         */
        public Map<Integer, Integer> getDayPrices() {
            return currentPriceMap;
        }
    }

    private static class LazyListMap extends LazyInitializingMap<Integer, List<Integer>> {

        @Override
        protected List<Integer> initialize(Object key) {
            return new ArrayList<Integer>(10);
        }
    }

    private static class ProductPriceCache extends LazyInitializingMap<Integer, Map<Integer, Integer>> {
        @Override
        protected Map<Integer, Integer> initialize(Object key) {
            return new HashMap<Integer, Integer>();
        }
    }
}
